// Copyright 2019 Google LLC.
#ifndef TextStyle_DEFINED
#define TextStyle_DEFINED

#include <optional>
#include <vector>
#include "include/core/SkColor.h"
#include "include/core/SkFont.h"
#include "include/core/SkFontMetrics.h"
#include "include/core/SkFontStyle.h"
#include "include/core/SkPaint.h"
#include "include/core/SkScalar.h"
#include "modules/skparagraph/include/DartTypes.h"
#include "modules/skparagraph/include/FontArguments.h"
#include "modules/skparagraph/include/ParagraphPainter.h"
#include "modules/skparagraph/include/TextShadow.h"

// TODO: Make it external so the other platforms (Android) could use it
#define DEFAULT_FONT_FAMILY "sans-serif"

namespace skia {
namespace textlayout {
static inline bool nearlyZero(SkScalar x, SkScalar tolerance = SK_ScalarNearlyZero)
{
    if (SkScalarIsFinite(x)) {
        return SkScalarNearlyZero(x, tolerance);
    }
    return false;
}

static inline bool nearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance = SK_ScalarNearlyZero)
{
    if (SkScalarIsFinite(x) && SkScalarIsFinite(x)) {
        return SkScalarNearlyEqual(x, y, tolerance);
    }
    // Inf == Inf, anything else is false
    return x == y;
}

// Multiple decorations can be applied at once. Ex: Underline and overline is
// (0x1 | 0x2)
enum TextDecoration {
    kNoDecoration = 0x0,
    kUnderline = 0x1,
    kOverline = 0x2,
    kLineThrough = 0x4,
};
constexpr TextDecoration AllTextDecorations[] = {
        kNoDecoration,
        kUnderline,
        kOverline,
        kLineThrough,
};

enum TextDecorationStyle { kSolid, kDouble, kDotted, kDashed, kWavy };

enum TextDecorationMode { kGaps, kThrough };

enum StyleType {
    kNone,
    kAllAttributes,
    kFont,
    kForeground,
    kBackground,
    kShadow,
    kDecorations,
    kLetterSpacing,
    kWordSpacing
};

struct Decoration {
    TextDecoration fType;
    TextDecorationMode fMode;
    SkColor fColor;
    TextDecorationStyle fStyle;
    SkScalar fThicknessMultiplier;

    bool operator == (const Decoration &other) const
    {
        return this->fType == other.fType && this->fMode == other.fMode && this->fColor == other.fColor &&
            this->fStyle == other.fStyle && this->fThicknessMultiplier == other.fThicknessMultiplier;
    }
};

// / Where to vertically align the placeholder relative to the surrounding text.
enum class PlaceholderAlignment {
    // / Match the baseline of the placeholder with the baseline.
    kBaseline,

    // / Align the bottom edge of the placeholder with the baseline such that the
    // / placeholder sits on top of the baseline.
    kAboveBaseline,

    // / Align the top edge of the placeholder with the baseline specified in
    // / such that the placeholder hangs below the baseline.
    kBelowBaseline,

    // / Align the top edge of the placeholder with the top edge of the font.
    // / When the placeholder is very tall, the extra space will hang from
    // / the top and extend through the bottom of the line.
    kTop,

    // / Align the bottom edge of the placeholder with the top edge of the font.
    // / When the placeholder is very tall, the extra space will rise from
    // / the bottom and extend through the top of the line.
    kBottom,

    // / Align the middle of the placeholder with the middle of the text. When the
    // / placeholder is very tall, the extra space will grow equally from
    // / the top and bottom of the line.
    kMiddle,
};

struct FontFeature {
    FontFeature(SkString name, int value) : fName(std::move(name)), fValue(value) {}
    bool operator == (const FontFeature &that) const
    {
        return fName == that.fName && fValue == that.fValue;
    }
    SkString fName;
    int fValue;
};

struct PlaceholderStyle {
    PlaceholderStyle() = default;
    PlaceholderStyle(SkScalar width, SkScalar height, PlaceholderAlignment alignment, TextBaseline baseline,
        SkScalar offset)
        : fWidth(width), fHeight(height), fAlignment(alignment), fBaseline(baseline), fBaselineOffset(offset)
    {}

    bool equals(const PlaceholderStyle &) const;

    SkScalar fWidth = 0;
    SkScalar fHeight = 0;
    PlaceholderAlignment fAlignment = PlaceholderAlignment::kBaseline;
    TextBaseline fBaseline = TextBaseline::kAlphabetic;
    // Distance from the top edge of the rect to the baseline position. This
    // baseline will be aligned against the alphabetic baseline of the surrounding
    // text.
    //
    // Positive values drop the baseline lower (positions the rect higher) and
    // small or negative values will cause the rect to be positioned underneath
    // the line. When baseline == height, the bottom edge of the rect will rest on
    // the alphabetic baseline.
    SkScalar fBaselineOffset = 0;
};

class TextStyle {
public:
    TextStyle() = default;
    TextStyle(const TextStyle &other) = default;
    TextStyle &operator = (const TextStyle &other) = default;

    TextStyle cloneForPlaceholder();

    bool equals(const TextStyle &other) const;
    bool equalsByFonts(const TextStyle &that) const;
    bool matchOneAttribute(StyleType styleType, const TextStyle &other) const;
    bool operator == (const TextStyle &rhs) const
    {
        return this->equals(rhs);
    }

    // Colors
    SkColor getColor() const
    {
        return fColor;
    }
    void setColor(SkColor color)
    {
        fColor = color;
    }

    bool hasForeground() const
    {
        return fHasForeground;
    }
    SkPaint getForeground() const
    {
        const SkPaint *paint = std::get_if<SkPaint>(&fForeground);
        return paint ? *paint : SkPaint();
    }
    ParagraphPainter::SkPaintOrID getForegroundPaintOrID() const
    {
        return fForeground;
    }
    void setForegroundPaint(SkPaint paint)
    {
        fHasForeground = true;
        fForeground = std::move(paint);
    }
    // DEPRECATED: prefer `setForegroundPaint`.
    void setForegroundColor(SkPaint paint)
    {
        setForegroundPaint(std::move(paint));
    }

    // Set the foreground to a paint ID.  This is intended for use by clients
    // that implement a custom ParagraphPainter that can not accept an SkPaint.
    void setForegroundPaintID(ParagraphPainter::PaintID paintID)
    {
        fHasForeground = true;
        fForeground = paintID;
    }
    void clearForegroundColor()
    {
        fHasForeground = false;
    }

    bool hasBackground() const
    {
        return fHasBackground;
    }
    SkPaint getBackground() const
    {
        const SkPaint *paint = std::get_if<SkPaint>(&fBackground);
        return paint ? *paint : SkPaint();
    }
    ParagraphPainter::SkPaintOrID getBackgroundPaintOrID() const
    {
        return fBackground;
    }
    void setBackgroundPaint(SkPaint paint)
    {
        fHasBackground = true;
        fBackground = std::move(paint);
    }
    // DEPRECATED: prefer `setBackgroundPaint`.
    void setBackgroundColor(SkPaint paint)
    {
        setBackgroundPaint(std::move(paint));
    }
    void setBackgroundPaintID(ParagraphPainter::PaintID paintID)
    {
        fHasBackground = true;
        fBackground = paintID;
    }
    void clearBackgroundColor()
    {
        fHasBackground = false;
    }

    // Decorations
    Decoration getDecoration() const
    {
        return fDecoration;
    }
    TextDecoration getDecorationType() const
    {
        return fDecoration.fType;
    }
    TextDecorationMode getDecorationMode() const
    {
        return fDecoration.fMode;
    }
    SkColor getDecorationColor() const
    {
        return fDecoration.fColor;
    }
    TextDecorationStyle getDecorationStyle() const
    {
        return fDecoration.fStyle;
    }
    SkScalar getDecorationThicknessMultiplier() const
    {
        return fDecoration.fThicknessMultiplier;
    }
    void setDecoration(TextDecoration decoration)
    {
        fDecoration.fType = decoration;
    }
    void setDecorationMode(TextDecorationMode mode)
    {
        fDecoration.fMode = mode;
    }
    void setDecorationStyle(TextDecorationStyle style)
    {
        fDecoration.fStyle = style;
    }
    void setDecorationColor(SkColor color)
    {
        fDecoration.fColor = color;
    }
    void setDecorationThicknessMultiplier(SkScalar m)
    {
        fDecoration.fThicknessMultiplier = m;
    }

    // Weight/Width/Slant
    SkFontStyle getFontStyle() const
    {
        return fFontStyle;
    }
    void setFontStyle(SkFontStyle fontStyle)
    {
        fFontStyle = fontStyle;
    }

    // Shadows
    size_t getShadowNumber() const
    {
        return fTextShadows.size();
    }
    std::vector<TextShadow> getShadows() const
    {
        return fTextShadows;
    }
    void addShadow(TextShadow shadow)
    {
        fTextShadows.emplace_back(shadow);
    }
    void resetShadows()
    {
        fTextShadows.clear();
    }

    // Font features
    size_t getFontFeatureNumber() const
    {
        return fFontFeatures.size();
    }
    std::vector<FontFeature> getFontFeatures() const
    {
        return fFontFeatures;
    }
    void addFontFeature(const SkString &fontFeature, int value)
    {
        fFontFeatures.emplace_back(fontFeature, value);
    }
    void resetFontFeatures()
    {
        fFontFeatures.clear();
    }

    // Font arguments
    const std::optional<FontArguments> &getFontArguments() const
    {
        return fFontArguments;
    }
    // The contents of the SkFontArguments will be copied into the TextStyle,
    // and the SkFontArguments can be safely deleted after setFontArguments returns.
    void setFontArguments(const std::optional<SkFontArguments> &args);

    SkScalar getFontSize() const
    {
        return fFontSize;
    }
    void setFontSize(SkScalar size)
    {
        fFontSize = size;
    }

    const std::vector<SkString> &getFontFamilies() const
    {
        return fFontFamilies;
    }
    void setFontFamilies(std::vector<SkString> families)
    {
        fFontFamilies = std::move(families);
    }

    SkScalar getBaselineShift() const
    {
        return fBaselineShift;
    }
    void setBaselineShift(SkScalar baselineShift)
    {
        fBaselineShift = baselineShift;
    }

    void setHeight(SkScalar height)
    {
        fHeight = height;
    }
    SkScalar getHeight() const
    {
        return fHeightOverride ? fHeight : 0;
    }

    void setHeightOverride(bool heightOverride)
    {
        fHeightOverride = heightOverride;
    }
    bool getHeightOverride() const
    {
        return fHeightOverride;
    }

    void setHalfLeading(bool halfLeading)
    {
        fHalfLeading = halfLeading;
    }
    bool getHalfLeading() const
    {
        return fHalfLeading;
    }

    void setLetterSpacing(SkScalar letterSpacing)
    {
        fLetterSpacing = letterSpacing;
    }
    SkScalar getLetterSpacing() const
    {
        return fLetterSpacing;
    }

    void setWordSpacing(SkScalar wordSpacing)
    {
        fWordSpacing = wordSpacing;
    }
    SkScalar getWordSpacing() const
    {
        return fWordSpacing;
    }

    SkTypeface *getTypeface() const
    {
        return fTypeface.get();
    }
    sk_sp<SkTypeface> refTypeface() const
    {
        return fTypeface;
    }
    void setTypeface(sk_sp<SkTypeface> typeface)
    {
        fTypeface = std::move(typeface);
    }

    SkString getLocale() const
    {
        return fLocale;
    }
    void setLocale(const SkString &locale)
    {
        fLocale = locale;
    }

    TextBaseline getTextBaseline() const
    {
        return fTextBaseline;
    }
    void setTextBaseline(TextBaseline baseline)
    {
        fTextBaseline = baseline;
    }

    void getFontMetrics(SkFontMetrics *metrics) const;

    bool isPlaceholder() const
    {
        return fIsPlaceholder;
    }
    void setPlaceholder()
    {
        fIsPlaceholder = true;
    }

private:
    static const std::vector<SkString> *kDefaultFontFamilies;

    Decoration fDecoration = { TextDecoration::kNoDecoration,
        // TODO: switch back to kGaps when (if) switching flutter to skparagraph
        TextDecorationMode::kThrough,
        // It does not make sense to draw a transparent object, so we use this as a default
        // value to indicate no decoration color was set.
        SK_ColorTRANSPARENT, TextDecorationStyle::kSolid,
        // Thickness is applied as a multiplier to the default thickness of the font.
        1.0f };

    SkFontStyle fFontStyle;

    std::vector<SkString> fFontFamilies = *kDefaultFontFamilies;

    SkScalar fFontSize = 14.0;
    SkScalar fHeight = 1.0;
    bool fHeightOverride = false;
    SkScalar fBaselineShift = 0.0f;
    // true: half leading.
    // false: scale ascent/descent with fHeight.
    bool fHalfLeading = false;
    SkString fLocale = {};
    SkScalar fLetterSpacing = 0.0;
    SkScalar fWordSpacing = 0.0;

    TextBaseline fTextBaseline = TextBaseline::kAlphabetic;

    SkColor fColor = SK_ColorWHITE;
    bool fHasBackground = false;
    ParagraphPainter::SkPaintOrID fBackground;
    bool fHasForeground = false;
    ParagraphPainter::SkPaintOrID fForeground;

    std::vector<TextShadow> fTextShadows;

    sk_sp<SkTypeface> fTypeface;
    bool fIsPlaceholder = false;

    std::vector<FontFeature> fFontFeatures;

    std::optional<FontArguments> fFontArguments;
};

typedef size_t TextIndex;
typedef SkRange<size_t> TextRange;
const SkRange<size_t> EMPTY_TEXT = EMPTY_RANGE;

struct Block {
    Block() = default;
    Block(size_t start, size_t end, const TextStyle &style) : fRange(start, end), fStyle(style) {}
    Block(TextRange textRange, const TextStyle &style) : fRange(textRange), fStyle(style) {}

    void add(TextRange tail)
    {
        SkASSERT(fRange.end == tail.start);
        fRange = TextRange(fRange.start, fRange.start + fRange.width() + tail.width());
    }

    TextRange fRange = EMPTY_RANGE;
    TextStyle fStyle;
};


typedef size_t BlockIndex;
typedef SkRange<size_t> BlockRange;
const size_t EMPTY_BLOCK = EMPTY_INDEX;
const SkRange<size_t> EMPTY_BLOCKS = EMPTY_RANGE;

struct Placeholder {
    Placeholder() = default;
    Placeholder(size_t start, size_t end, const PlaceholderStyle &style, const TextStyle &textStyle,
        BlockRange blocksBefore, TextRange textBefore)
        : fRange(start, end), fStyle(style), fTextStyle(textStyle), fBlocksBefore(blocksBefore), fTextBefore(textBefore)
    {}

    TextRange fRange = EMPTY_RANGE;
    PlaceholderStyle fStyle;
    TextStyle fTextStyle;
    BlockRange fBlocksBefore;
    TextRange fTextBefore;
};
} // namespace textlayout
} // namespace skia

#endif // TextStyle_DEFINED
